//
//  NSObject+MZBlockKVO.m
//  MZToolkit测试
//
//  Created by shenjialin on 2017/4/6.
//  Copyright © 2017年 shenjialin. All rights reserved.
//

#import "NSObject+MZBlockKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
//@import ObjectiveC.runtime;
//@import ObjectiveC.message;

typedef NS_ENUM(int, MZObserverContext) {
    MZObserverContextKey,
    MZObserverContextKeyWithChange,
    MZObserverContextManyKeys,
    MZObserverContextManyKeysWithChange
};

@interface MZObserver : NSObject {
    BOOL _isObserving;
}

@property (nonatomic, readonly, assign) id observee;
@property (nonatomic, readonly) NSMutableArray *keyPaths;
@property (nonatomic, readonly) id task;
@property (nonatomic, readonly) MZObserverContext context;

- (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(MZObserverContext)context task:(id)task;

@end
static void *MZObserverBlocksKey = &MZObserverBlocksKey;
static void *MZBlockObservationContext = &MZBlockObservationContext;

@implementation MZObserver

- (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(MZObserverContext)context task:(id)task{
    if ((self = [super init])) {
        _observee = observee;
        _keyPaths = [keyPaths mutableCopy];
        _context = context;
        _task = [task copy];
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    if (context != MZBlockObservationContext) return;
    
    @synchronized(self) {
        switch (self.context) {
            case MZObserverContextKey: {
                void (^task)(id) = self.task;
                task(object);
                break;
            }
            case MZObserverContextKeyWithChange: {
                void (^task)(id, NSDictionary *) = self.task;
                task(object, change);
                break;
            }
            case MZObserverContextManyKeys: {
                void (^task)(id, NSString *) = self.task;
                task(object, keyPath);
                break;
            }
            case MZObserverContextManyKeysWithChange: {
                void (^task)(id, NSString *, NSDictionary *) = self.task;
                task(object, keyPath, change);
                break;
            }
        }
    }
}

- (void)mzStartObservingWithOptions:(NSKeyValueObservingOptions)options{
    @synchronized(self) {
        if (_isObserving) return;
        
        for (NSString *keyPath in self.keyPaths) {
            [self.observee addObserver:self forKeyPath:keyPath options:options context:MZBlockObservationContext];
        }
        
        _isObserving = YES;
    }
}

- (void)mzStopObservingKeyPath:(NSString *)keyPath{
    NSParameterAssert(keyPath);
    
    @synchronized (self) {
        if (!_isObserving) return;
        if (![self.keyPaths containsObject:keyPath]) return;
        
        NSObject *observee = self.observee;
        if (!observee) return;
        
        [self.keyPaths removeObject: keyPath];
        keyPath = [keyPath copy];
        
        if (!self.keyPaths.count) {
            _task = nil;
            _observee = nil;
            _keyPaths = nil;
        }
        
        [observee removeObserver:self forKeyPath:keyPath context:MZBlockObservationContext];
    }
}

- (void)mzStopObservingLocked{
    if (!_isObserving) return;
    
    _task = nil;
    
    NSObject *observee = self.observee;
    NSArray *keyPaths = [self.keyPaths copy];
    
    _observee = nil;
    _keyPaths = nil;
    
    for (NSString *keyPath in keyPaths) {
        [observee removeObserver:self forKeyPath:keyPath context:MZBlockObservationContext];
    }
}

- (void)mzStopObserving{
    if (_observee == nil) return;
    
    @synchronized (self) {
        [self mzStopObservingLocked];
    }
}

- (void)dealloc{
    if (self.keyPaths) {
        [self mzStopObservingLocked];
    }
}

@end

static const NSUInteger MZKeyValueObservingOptionWantsChangeDictionary = 0x1000;
@implementation NSObject (MZBlockKVO)

//用来管理被观察者的无序序列，对被观察者持有强引用
+ (NSMutableSet *)mzObservedClassesHash{
    static dispatch_once_t onceToken;
    static NSMutableSet *swizzledClasses = nil;
    dispatch_once(&onceToken, ^{
        swizzledClasses = [[NSMutableSet alloc] init];
    });
    
    return swizzledClasses;
}


//记录自身的观察者
- (void)mzSetObserverBlocks:(NSMutableDictionary *)dict{
    objc_setAssociatedObject(self, MZObserverBlocksKey, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSMutableDictionary *)mzObserverBlocks{
    return objc_getAssociatedObject(self, MZObserverBlocksKey);
}

- (void)mzAddObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSDictionary *change))task{
    MZObserverContext context = (options == 0) ? MZObserverContextKey : MZObserverContextKeyWithChange;
    options = options & (~MZKeyValueObservingOptionWantsChangeDictionary);
    [self mzAddObserverForKeyPaths:@[keyPath] identifier:identifier options:options context:context task:task];
}

- (void)mzAaddObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSString *keyPath, NSDictionary *change))task{
    MZObserverContext context = (options == 0) ? MZObserverContextManyKeys : MZObserverContextManyKeysWithChange;
    options = options & (~MZKeyValueObservingOptionWantsChangeDictionary);
    [self mzAddObserverForKeyPaths:keyPaths identifier:identifier options:options context:context task:task];
}

- (void)mzAddObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(MZObserverContext)context task:(id)task{
    Class classToSwizzle = self.class;
    //拿到管理被观察者的无序序列
    NSMutableSet *classes = self.class.mzObservedClassesHash;
    @synchronized (classes) {
        //拿到被观察者的类名
        NSString *className = NSStringFromClass(classToSwizzle);
        if (![classes containsObject:className]) {
            //拿到dealloc的selector方法
            SEL deallocSelector = sel_registerName("dealloc");
            
            __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;
            
            //定义新的dealloc方法
            id newDealloc = ^(__unsafe_unretained id objSelf) {
                [objSelf mzRemoveAllBlockObservers];
                
                if (originalDealloc == NULL) {
                    //调用父类的dealloc方法
                    struct objc_super superInfo = {
                        .receiver = objSelf,
                        .super_class = class_getSuperclass(classToSwizzle)
                    };
                    
                    void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
                    msgSend(&superInfo, deallocSelector);
                } else {
                    //调用自身的dealloc方法
                    originalDealloc(objSelf, deallocSelector);
                }
            };
            
            //拿到newDealloc方法的实现指针
            IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
            
            //给class添加dealloc方法
            if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {
                //类中已经实现dealloc方法
                
                //拿到self中的dealloc方法
                Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);
                
                //返回dealloc方法的实现地址
                originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_getImplementation(deallocMethod);
                
                //设置dealloc方法的新的实现指针为newDeallocIMP, 返回旧的实现指针
                originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP);
            }
            //记录被观察者
            [classes addObject:className];
        }
    }
    
    NSMutableDictionary *dict;
    //实例化MZObserver对像
    MZObserver *observer = [[MZObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
    //对被观察者进行观察
    [observer mzStartObservingWithOptions:options];
    
    @synchronized (self) {
        dict = [self mzObserverBlocks];
        
        if (dict == nil) {
            dict = [NSMutableDictionary dictionary];
            [self mzSetObserverBlocks:dict];
        }
    }
    //记录观察者
    dict[identifier] = observer;

}

- (void)mzRemoveObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)token{
    NSMutableDictionary *dict;
    
    @synchronized (self) {
        dict = [self mzObserverBlocks];
        if (!dict) return;
    }
    
    MZObserver *observer = dict[token];
    [observer mzStopObservingKeyPath:keyPath];
    
    if (observer.keyPaths.count == 0) {
        [dict removeObjectForKey:token];
    }
    
    if (dict.count == 0) [self mzSetObserverBlocks:nil];
}
- (void)mzRemoveObserversWithIdentifier:(NSString *)token{
    NSMutableDictionary *dict;
    
    @synchronized (self) {
        dict = [self mzObserverBlocks];
        if (!dict) return;
    }
    
    MZObserver *observer = dict[token];
    [observer mzStopObserving];
    
    [dict removeObjectForKey:token];
    
    if (dict.count == 0) [self mzSetObserverBlocks:nil];
}
- (void)mzRemoveAllBlockObservers{
    NSDictionary *dict;
    
    @synchronized (self) {
        dict = [[self mzObserverBlocks] copy];
        [self mzSetObserverBlocks:nil];
    }
    
    for (NSString *key in dict.allKeys) {
        //让观察者停止观察
        [dict[key] mzStopObserving];
    }
}

#pragma mark 存储数据

- (NSString *)mzArchiverPath{
    NSString *path=[NSHomeDirectory() stringByAppendingString:@"/Library/Caches/MZArchiver"];
    NSFileManager * fm =[NSFileManager defaultManager];
    BOOL isDir=true;
    if (![fm fileExistsAtPath:path isDirectory:&isDir]) {
        BOOL isYes = [fm createDirectoryAtPath:path withIntermediateDirectories:false attributes:nil error:nil];
        if (!isYes) {
            return @"";
        }
    }
    return path;
}

//归档数据并缓存，infor是遵从NSCoding的对象，key是唯一对应的关键字
- (BOOL)mzKeyedArchiverWithInfor:(id <NSCoding>)infor withKey:(NSString *)key{
    if (key.length==0) {
        return NO;
    }
    NSMutableData *data=[[NSMutableData alloc]init];
    //进行归档
    NSKeyedArchiver *aCoder=[[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
    [aCoder encodeObject:infor forKey:key];
    [aCoder finishEncoding];
    
    NSString *sourcePath=[self mzArchiverPath];
    if (sourcePath.length==0) {
        //如果根目录没有创建出来，直接返回保存失败
        return false;
    }
    NSString *path=[[self mzArchiverPath] stringByAppendingFormat:@"/mz-%@",key];
    NSFileManager * fm =[NSFileManager defaultManager];
    if ([fm fileExistsAtPath:path]) {
        BOOL isYes = [fm removeItemAtPath:path error:nil];
        if (isYes) {
            //删除成功创建目录
            return [fm createFileAtPath:path contents:data attributes:nil];
        }else{
            //删除失败直接返回保存失败
            return isYes;
        }
    }else{
        return [fm createFileAtPath:path contents:data attributes:nil];
    }
}

//解归档数据，key是关键字
- (nullable id <NSCoding>)mzKeyedUnArchiverWithKey:(NSString *)key{
    if (key.length==0) {
        return nil;
    }
    NSString *sourcePath=[self mzArchiverPath];
    if (sourcePath.length==0) {
        //如果根目录没有创建出来，直接返回nil
        return nil;
    }
    NSString *path=[[self mzArchiverPath] stringByAppendingFormat:@"/mz-%@",key];
    //先读出文件
    NSFileManager * fm =[NSFileManager defaultManager];
    if ([fm fileExistsAtPath:path]) {
        NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:path];
        if (fh==nil) {
            //NSFileHandle  没有创建成功直接返回nil
            return nil;
        }
        NSData *data = [fh readDataToEndOfFile];
        if (data.length==0) {
            return nil;
        }
        NSKeyedUnarchiver *acoder=[[NSKeyedUnarchiver alloc]initForReadingWithData:data];
        //从包中取出数据
        id<NSCoding> info = [acoder decodeObjectForKey:key];
        //结束解归档
        [acoder finishDecoding];
        return info;
    }
    return nil;
}

- (void)mzReadAllAttrWithDecoder:(NSCoder *)aDecoder {
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [aDecoder decodeObjectForKey:key];
        if (nil != value) {
            [self setValue:value forKey:key];
        }
    }
    free(ivars);
}

- (void)mzSaveAllAttrWithCoder:(NSCoder *)aCoder {
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        if ([value respondsToSelector:@selector(encodeWithCoder:)]) {
            [aCoder encodeObject:value forKey:key];
        }
    }
    free(ivars);
}

//根据关键字key删除缓存
- (BOOL)mzRemoveArchiveDataWithKey:(NSString *)key{
    if (key.length==0) {
        return NO;
    }
    NSString *sourcePath=[self mzArchiverPath];
    if (sourcePath.length==0) {
        //如果根目录没有创建出来，直接返回true
        return true;
    }
    NSString *path=[[self mzArchiverPath] stringByAppendingFormat:@"/mz-%@",key];
    //先读出文件
    NSFileManager * fm =[NSFileManager defaultManager];
    if ([fm fileExistsAtPath:path]) {
        return [fm removeItemAtPath:path error:nil];
    }
    return true;
}

//删除所有缓存
- (BOOL)mzRemoveAllArchiveData{
    NSString *sourcePath=[self mzArchiverPath];
    if (sourcePath.length==0) {
        //如果根目录没有创建出来，直接返回true
        return true;
    }
    NSFileManager * fm =[NSFileManager defaultManager];
    return [fm removeItemAtPath:sourcePath error:nil];
}

@end
